package com.zlx.mall.basic.util.crypto;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

import javax.crypto.Cipher;
import java.io.ByteArrayInputStream;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @author qingke
 **/
@Slf4j
public final class RSAUtil {

    private static final String ALGORITHM = "RSA";
    private static final int KEY_SIZE = 2048;
    private static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding";
    private static final String SIGNATURE_ALGORITHM_MD5 = "MD5withRSA";
    private static final String SIGNATURE_ALGORITHM_SHA1 = "SHA1withRSA";
    private static final String PUBLIC_KEY_BEGIN = "-----BEGIN RSA PUBLIC KEY-----";
    private static final String PUBLIC_KEY_END = "-----END RSA PUBLIC KEY-----";
    private static final String PRIVATE_KEY_BEGIN = "-----BEGIN RSA PRIVATE KEY-----";
    private static final String PRIVATE_KEY_END = "-----END RSA PRIVATE KEY-----";

    private RSAUtil() {
    }

    public static KeyPair generateKeyPair() {
        try {
            KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITHM);
            // 密钥长度范围:512～2048,一般1024
            generator.initialize(KEY_SIZE);
            return generator.genKeyPair();
        } catch (Exception e) {
            log.error("###### RsaUtil.generateKeyPair Exception", e);
            throw new IllegalStateException("generateKeyPair Exception", e);
        }
    }

    /**
     * 将RSA公钥编码为BASE64字符串
     */
    public static String encodePublicKey(RSAPublicKey publicKey, boolean excludeIdentifier) {
        String encode = MoreBase64Utils.encodeToString(publicKey.getEncoded());
        if (excludeIdentifier) {
            return encode;
        }
        return PUBLIC_KEY_BEGIN + "\n" + encode + "\n" + PUBLIC_KEY_END;
    }

    /**
     * 将RSA私钥编码为BASE64字符串
     */
    public static String encodePrivateKey(RSAPrivateKey privateKey, boolean excludeIdentifier) {
        String encode = MoreBase64Utils.encodeToString(privateKey.getEncoded());
        if (excludeIdentifier) {
            return encode;
        }
        return PRIVATE_KEY_BEGIN + "\n" + encode + "\n" + PRIVATE_KEY_END;
    }

    public static void printPublicKeyInfo(RSAPublicKey publicKey) {
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("RSA Public Key Info:").append("\n")
                    .append("Format=").append(publicKey.getFormat()).append("\n")
                    .append("Algorithm=").append(publicKey.getAlgorithm()).append("\n")
                    .append("Modulus.length=").append(publicKey.getModulus().bitLength()).append("\n")
                    .append("Modulus=").append(publicKey.getModulus().toString()).append("\n")
                    .append("PublicExponent.length=").append(publicKey.getPublicExponent().bitLength()).append("\n")
                    .append("PublicExponent=").append(publicKey.getPublicExponent().toString()).append("\n");
        } catch (Exception e) {
            builder.append(e);
        }
        log.info("{}", builder);
    }

    public static void printPrivateKeyInfo(RSAPrivateKey privateKey) {
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("RSA Private Key Info:").append("\n")
                    .append("Format=").append(privateKey.getFormat()).append("\n")
                    .append("Algorithm=").append(privateKey.getAlgorithm()).append("\n")
                    .append("Modulus.length=").append(privateKey.getModulus().bitLength()).append("\n")
                    .append("Modulus=").append(privateKey.getModulus().toString()).append("\n")
                    .append("PrivateExponent.length=").append(privateKey.getPrivateExponent().bitLength()).append("\n")
                    .append("PrivateExponent=").append(privateKey.getPrivateExponent().toString()).append("\n");
        } catch (Exception e) {
            builder.append(e);
        }
        log.info("{}", builder);
    }

    /**
     * 根据二进制BASE64编码的X509证书生成X509证书
     */
    public static X509Certificate decodeCertificate(byte[] base64) {
        Assert.state((base64 != null && base64.length > 0), "Input is empty");

        try {
            ByteArrayInputStream stream = new ByteArrayInputStream(base64);
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            return (X509Certificate) factory.generateCertificate(stream);
        } catch (Exception e) {
            log.error("###### RsaUtil.generateCertificate Exception", e);
        }
        return null;
    }

    /**
     * 根据字符串BASE64编码的X509证书生成X509证书
     */
    public static X509Certificate decodeCertificate(String base64) {
        base64 = ignoreIdentifier(base64);
        byte[] data = MoreBase64Utils.decodeFromMimeString(base64);
        return decodeCertificate(data);
    }

    /**
     * 根据二进制BASE64编码的公钥生成RSA公钥
     */
    public static RSAPublicKey decodePublicKey(byte[] base64) {
        Assert.state((base64 != null && base64.length > 0), "Input is empty");

        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            // Only RSAPublicKeySpec and X509EncodedKeySpec supported for RSA public keys
            return (RSAPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(base64));
        } catch (Exception e) {
            throw new IllegalStateException("generatePublicKey exception", e);
        }
    }

    /**
     * 根据字符串BASE64编码的公钥生成RSA公钥
     */
    public static RSAPublicKey decodePublicKey(String base64) {
        base64 = ignoreIdentifier(base64);
        byte[] data = MoreBase64Utils.decodeFromMimeString(base64);
        return decodePublicKey(data);
    }

    /**
     * 根据二进制BASE64编码的私钥生成RSA私钥
     */
    public static RSAPrivateKey decodePrivateKey(byte[] base64) {
        Assert.state((base64 != null && base64.length > 0), "Input is empty");

        try {
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            try {
                return (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(base64));
            } catch (InvalidKeySpecException ignore) {
                return (RSAPrivateKey) keyFactory.generatePrivate(new X509EncodedKeySpec(base64));
            }
        } catch (Exception e) {
            throw new IllegalStateException("generatePrivateKey exception", e);
        }
    }

    /**
     * 根据字符串BASE64编码的私钥生成RSA私钥
     */
    public static RSAPrivateKey decodePrivateKey(String base64) {
        base64 = ignoreIdentifier(base64);
        byte[] data = MoreBase64Utils.decodeFromMimeString(base64);
        return decodePrivateKey(data);
    }

    private static String ignoreIdentifier(String str) {
        StringBuilder builder = new StringBuilder();
        String[] lines = str.split("\n");
        for (String line : lines) {
            // 忽略证书及秘钥串首尾的标识符
            if (line.charAt(0) != '-') {
                builder.append(line).append('\n');
            }
        }
        return builder.toString();
    }

    /**
     * 使用RSA公钥加密对称加密算法所使用的明文密码
     * <p>
     * 说明:RSA只支持小数据加密,对于大批量数据加密,不建议对数据分段加密,强烈建议使用以下方案:
     *
     * </p>
     * 1. 客户端选择合适的对称加密算法(如AES、RC4、XXTEA),该算法须服务端也支持;
     * <br>
     * 2. 客户端随机生成一个对称加密算法所用的密码,使用该密码加密数据,再使用RSA公钥加密该密码;
     * <br>
     * 3. 服务端使用RSA私钥解密出对称加密算法所用的密码,再使用该密码及同样的对称加密算法解密数据
     * <br>
     * 推荐查阅这篇文章帮助理解:
     * <a href="https://www.cnblogs.com/JeffreySun/archive/2010/06/24/1627247.html">Doc.</a>
     */
    public static byte[] encrypt(RSAPublicKey publicKey, byte[] content) {
        Assert.state((content != null && content.length > 0), "Plain SecretKey is empty");

        try {
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return cipher.doFinal(content);
        } catch (Exception e) {
            throw new IllegalStateException("encrypt exception", e);
        }
    }

    public static String encryptToBase64(RSAPublicKey publicKey, byte[] content) {
        return MoreBase64Utils.encodeToString(encrypt(publicKey, content));
    }

    /**
     * 使用RSA私钥解密对称加密算法所使用的密文密码
     */
    public static byte[] decrypt(RSAPrivateKey privateKey, byte[] encryptedContent) {
        Assert.state((encryptedContent != null && encryptedContent.length > 0), "Encrypted SecretKey is empty");

        try {
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(encryptedContent);
        } catch (Exception e) {
            throw new IllegalStateException("decrypt exception", e);
        }
    }

    public static byte[] decryptFromBase64(RSAPrivateKey privateKey, String base64) {
        return decrypt(privateKey, MoreBase64Utils.decodeFromMimeString(base64));
    }

    /**
     * 使用私钥对数据签名
     * 支持的算法参见
     * <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Signature">Doc</a>
     */
    public static byte[] signature(RSAPrivateKey privateKey, String algorithm, byte[] content) {
        Assert.state((content != null && content.length > 0), "Content is empty");
        Assert.notNull(privateKey, "PrivateKey is null");

        try {
            Signature signature = Signature.getInstance(algorithm);
            signature.initSign(privateKey);
            signature.update(content);
            return signature.sign();
        } catch (Exception e) {
            throw new IllegalStateException("signature exception", e);
        }
    }

    public static String signatureMD5ToBase64(RSAPrivateKey privateKey, byte[] content) {
        return MoreBase64Utils.encodeToString(signature(privateKey, SIGNATURE_ALGORITHM_MD5, content));
    }

    public static String signatureSHA1ToBase64(RSAPrivateKey privateKey, byte[] content) {
        return MoreBase64Utils.encodeToString(signature(privateKey, SIGNATURE_ALGORITHM_SHA1, content));
    }

    /**
     * 使用公钥验证数据签名
     */
    public static boolean verify(RSAPublicKey publicKey, String algorithm, byte[] sign, byte[] content) {
        Assert.notNull(publicKey, "PublicKey is null");
        Assert.state((content != null && content.length > 0), "Content is empty");
        Assert.state((sign != null && sign.length > 0), "Sign is empty");

        try {
            Signature signature = Signature.getInstance(algorithm);
            signature.initVerify(publicKey);
            signature.update(content);
            return signature.verify(sign);
        } catch (Exception e) {
            throw new IllegalStateException("verify exception", e);
        }
    }

    public static boolean verifyMD5FromBase64(RSAPublicKey publicKey, String sign, byte[] content) {
        return verify(publicKey, SIGNATURE_ALGORITHM_MD5, MoreBase64Utils.decodeFromMimeString(sign), content);
    }

    public static boolean verifySHA1FromBase64(RSAPublicKey publicKey, String sign, byte[] content) {
        return verify(publicKey, SIGNATURE_ALGORITHM_SHA1, MoreBase64Utils.decodeFromMimeString(sign), content);
    }

}
